Skip to content

Fix time-sync median: sort clock offsets numerically - #165

Open
xn101de wants to merge 1 commit into
snapcast:developfrom
xn101de:fix/timesync-median-numeric-sort
Open

xn101de wants to merge 1 commit into
snapcast:developfrom
xn101de:fix/timesync-median-numeric-sort

Conversation

@xn101de

@xn101de xn101de commented Jun 18, 2026

Copy link
Copy Markdown

TimeProvider.setDiff computes the median client/server clock offset to drive audio/video synchronization, but sorted the offset buffer with a bare Array.prototype.sort(). With no comparator, sort() coerces elements to strings and orders them lexicographically, so the "median" was taken from a wrongly ordered array (e.g. [2, 10, -5, 100] -> [-5, 10, 100, 2]).

The resulting offset is unstable and frequently wrong, corrupting every serverTime() calculation and causing playback drift and "Chunk too old, dropping" stalls.

Sort numerically with an explicit comparator.

TimeProvider.setDiff computes the median client/server clock offset to
drive audio/video synchronization, but sorted the offset buffer with a
bare Array.prototype.sort(). With no comparator, sort() coerces elements
to strings and orders them lexicographically, so the "median" was taken
from a wrongly ordered array (e.g. [2, 10, -5, 100] -> [-5, 10, 100, 2]).

The resulting offset is unstable and frequently wrong, corrupting every
serverTime() calculation and causing playback drift and "Chunk too old,
dropping" stalls.

Sort numerically with an explicit comparator.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
pbtrung added a commit to pbtrung/snapweb that referenced this pull request Sep 30, 2026
Ports the fixes from upstream snapcast#165 and snapcast#166 onto the
refactored components.

Audio:
- Sort the clock offsets numerically in TimeProvider.setDiff. A bare
  sort() ordered them as strings, so the median offset was wrong, which
  skewed serverTime() and caused drift and dropped chunks
- Normalize PCM samples by 2^(bits-1) instead of 2^bits, which played
  all audio about 6 dB too quiet
- Write the UTF-8 byte length into serialized JSON messages instead of the
  UTF-16 string length, which truncated payloads with non-ASCII characters
- Generate the client id with crypto.randomUUID() where available

React:
- Register the prefers-color-scheme listener once in an effect with
  cleanup instead of adding a new listener on every render
- Use functional updates for the remaining force-update counters
- Return 0 instead of NaN for the volume of a group with no clients

Cleanup:
- Remove no-op clamp expressions in the MediaSession seek handlers
- Remove the large commented-out sample server JSON

Tests:
- Export the message classes and TimeProvider from snapstream.ts and add
  unit tests for the median, the non-ASCII JSON round trip, Hello and
  Time messages, and client id generation
pbtrung added a commit to pbtrung/snapweb that referenced this pull request Sep 30, 2026
README:
- Note the Node.js requirement (20.19+ or 22.12+, from Vite 8)
- Add Test and Code style sections covering the unit, coverage and
  Snapserver integration test commands, Prettier and ESLint
- Point out that the prebuilt zip and Debian releases are upstream
  Snapweb's, since this repository no longer builds Debian packages
- Remove the Screenshot and Contributing sections, and the screenshots in
  docs/images

Changelog:
- Add the 0.9999 entry: the time sync and audio fixes (including upstream
  PRs snapcast#165 and snapcast#166), the React and SnapControl fixes, lazy loading and
  bundle splitting, package updates, tests, Prettier and removed tooling
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant